package com.atguigu.gmall.item.service.impl;

import com.atguigu.gmall.common.cache.BloomName;
import com.atguigu.gmall.common.cache.GmallCache;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.feign.product.SkuInfoFeignClient;
import com.atguigu.gmall.feign.search.SkuEsFeignClient;
import com.atguigu.gmall.item.service.ItemService;
import com.atguigu.gmall.model.product.BaseCategoryView;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.model.product.SpuSaleAttr;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import springfox.documentation.annotations.Cacheable;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author dyqstart
 * @create 2021-08-07-19:22
 * @since 1.0.0
 */
@Service
@Slf4j
public class ItemServiceImpl implements ItemService {

    //临时保存数据
//    private Map<String, Object> cache = new HashMap<>();

    @Autowired
    SkuInfoFeignClient skuInfoFeignClient;

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Autowired
    RedissonClient redissonClient;

    @Qualifier("corePool")
    @Autowired
    ThreadPoolExecutor executor;

    @Autowired
    SkuEsFeignClient skuEsFeignClient;


    @SneakyThrows  //使用分布式锁做的方法
    public Map<String, Object> getSkuInfoWithRedissonDistributeLock(Long skuId) {
        /**
         * 1、原生方式
         */
//        return getSkuInfoWithRedisLock(skuId, false, null);

        //拿到锁对象
        RLock lock = redissonClient.getLock("lock");

        //2、Redsson版
        Map<String, Object> cache = queryFromCache(skuId);
        if (cache == null) {
            log.info("redis缓存没命中，准备查询数据库，，，");
            log.info("加锁、、、、");
            try {
                lock.lock();
                log.info("加锁成功。。。。尝试继续命中缓存");
                Map<String, Object> cacheAgain = queryFromCache(skuId);
                if (cacheAgain == null) {
                    //未命中缓存，查数据库
                    log.info("未命中缓存，开始查询数据库");
                    Thread.sleep(300);
                    Map<String, Object> data = getFromServiceItemFeign(skuId);
                    saveToCache(data, skuId);
                    return data;
                }
                log.info("命中缓存。。。。直接返回");
                return cacheAgain;
            } finally {
                //解锁
                log.info("解锁。。。。。");
                lock.unlock();
            }

        }
        log.info("缓存命中，直接返回");
        return cache;
    }


    //TODO
    @GmallCache(
//            bloomPrefix = RedisConst.SKUKEY_PREFIX,
//            bloomSuffix = RedisConst.SKUKEY_SUFFIX,

            cacheKeyExpr =
                    RedisConst.SKUKEY_PREFIX +
                    "#{#args[0]}" +
                    RedisConst.SKUKEY_SUFFIX,

            ttl = 1000 * 60 * 30,
            bloomName = BloomName.SKU,
            missDataTtl = 1000 * 60 * 10
    )

    @Override
    public Map<String, Object> getSkuInfo(Long skuId) {
        log.info("要查数据库了............");
        //查数据库
        Map<String, Object> map = getFromServiceItemFeign(skuId);
        return map;
    }

    /**
     * 节省资源消耗，采用延迟更新
     * @param skuId
     */
    @Override
    public void updateSkuHotScore(Long skuId) {
//        skuEsFeignClient.updateSkuHotScore(skuId,10L);
        //100 Redis更新
        ZSetOperations<String, String> zset = stringRedisTemplate.opsForZSet();
        Double hotScore = zset.incrementScore("hotScore", skuId.toString(), 1);
        if (hotScore % 10 == 0){
            skuEsFeignClient.updateSkuHotScore(skuId,hotScore.longValue());
        }

    }

//    @GmallCacheEvict() //把指定的数据清除， prefix + skuInfo.skuId  +  suffix
    //看谷粒商城的springcache
//    public void update(SkuInfo skuInfo){
//        //灵活，方法修改了数据，手动清除缓存
//
//    }


    /**
     * 分布式锁版的商品详情
     * 1、有锁的过期时间
     * 2、加锁 删锁
     * 3、锁的续期  10s 要给锁不断续期，业务不中断，锁要自动延长时间？
     * 后台启动一个daemon线程，每隔5秒，续满时间重新开始倒计时、 redisson
     *
     * @param skuId
     * @return
     */
    public Map<String, Object> getSkuInfoWithRedisLock(Long skuId, Boolean locked, String beforeToken) throws InterruptedException {

        System.out.println("Thread.currentThread() =" + Thread.currentThread());

        Map<String, Object> cache = queryFromCache(skuId);
        //1、先判断缓存中是否存在
        if (cache != null) {
            //2、缓存中有用缓存的
            System.out.println("缓存命中");
            log.info("缓存命中");

            return cache;
        } else {
            //3、缓存中没有,查数据库，放入缓存，再返回
            //3.1 站锁
            log.info("缓存不命中，开始抢锁.............");

            String token = beforeToken;
            Boolean aBoolean = locked;
            if (!locked) {//已经有锁锁定

                token = UUID.randomUUID().toString();
                //SETNX
                aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock", token, 20, TimeUnit.SECONDS);
            }
            if (aBoolean) {
                //3.2站锁成功
                try {
                    log.info("抢占成功..............");
                    cache = getFromServiceItemFeign(skuId);
                    //4、远程查询后放入缓存
                    saveToCache(cache, skuId);
                } finally {

                    //删除锁
//                    stringRedisTemplate.delete("lock");
                    //lua脚本  原子执行
                    String script = "if redis.call('get', KEYS[1]) == ARGV[1] " +
                            "then return redis.call('del', KEYS[1]) else return 0 end";
                    stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList("lock"), token);
                    log.info("删除锁成功............");
                }
            } else {

                log.info("没抢成功，开始尝试自旋站锁............");
                //3.3站锁失败
                while (true) {
                    log.info("自旋中..........");
                    Thread.sleep(200);
                    String token2 = UUID.randomUUID().toString();
                    Boolean absent = stringRedisTemplate.opsForValue().setIfAbsent("lock", token2);
                    if (absent) {
                        //自旋站锁成功
                        log.info("自旋占锁成功.........");

                        cache = getSkuInfoWithRedisLock(skuId, absent, token2);

                        //lua脚本  原子执行
                        String script = "if redis.call('get', KEYS[1]) == ARGV[1] " +
                                "then return redis.call('del', KEYS[1]) else return 0 end";
                        stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList("lock"), token2);

                        return cache;
                    }

//                    try {
//                        //自旋站锁成功
//                        log.info("自旋占锁成功.........");
//
//                        cache = getSkuInfoWithRedisLock(skuId);
//                    } finally {
//                        //删除锁，一定得执行 必须是原子性
////                        String lock = stringRedisTemplate.opsForValue().get("lock");
////                        if (token.equals(lock)) {
////                            stringRedisTemplate.delete("lock");
////                            System.out.println("删除分布式锁.............");
////                            log.info("删除分布式锁..............");
////                        }
//                        //lua脚本  原子执行
//                        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
//                        stringRedisTemplate.execute(new DefaultRedisScript<>(script), Arrays.asList("lock"));
//
//                    }
                }
            }
        }
        return cache;
    }

    /**
     * 通过skuId查缓存
     *
     * @param skuId
     * @return
     */
    @SneakyThrows //自己设置的使用原生redis操作实现的分布式可重入锁业务
    public Map<String, Object> queryFromCache(Long skuId) {
        ObjectMapper mapper = new ObjectMapper();
        ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
        String redisContent = operations.get("sku:info:" + skuId);

        if (StringUtils.isEmpty(redisContent)) {
            return null;
        } else {
            return mapper.readValue(redisContent, new TypeReference<Map<String, Object>>() {
            });
        }
    }


    /**
     * 将查询数据库后得到的数据放入缓存
     *
     * @param data
     * @param skuId
     */
    @SneakyThrows
    void saveToCache(Map<String, Object> data, Long skuId) {
        ObjectMapper objectMapper = new ObjectMapper();
        String string = objectMapper.writeValueAsString(data);

        ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
        operations.set("sku:info:" + skuId, string);
    }


//    /**
//     * 远程调用 service-product 查询出和当前 skuId 对应的所有信息
//     *
//     * @param skuId
//     * @return
//     */
//    @Override
//    public Map<String, Object> getSkuInfo(Long skuId) {
//
//        ObjectMapper mapper = new ObjectMapper();
//
//        ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
//
//        //1、先去缓存确认是否存在
//        String s = operations.get("sku:info" + skuId);
//        if (StringUtils.isEmpty(s)) {
//            //缓存中没有
//            Map<String, Object> fromServiceItemFeign = getFromServiceItemFeign(skuId);
//
//            String jsonStr = null;
//            try {
//                jsonStr = mapper.writeValueAsString(fromServiceItemFeign);
//            } catch (JsonProcessingException e) {
//                e.printStackTrace();
//            }
//            //先给redis存一份
//            operations.set("sku:info" + skuId, jsonStr);
//            //返回数据
//            return fromServiceItemFeign;
//        } else {
//            Map<String, Object> stringObjectMap = null;
//            try {
//                stringObjectMap = mapper.readValue(s, new TypeReference<Map<String, Object>>() {
//                });
//            } catch (JsonProcessingException e) {
//                e.printStackTrace();
//            }
//            return stringObjectMap;
//        }
//    }


    /**
     * 异步编排
     * 线程复用，减少线程创建时间，增大吞吐量
     * 控制住所有并发，防止无限消耗
     *
     * @param skuId
     * @return
     */
    private Map<String, Object> getFromServiceItemFeign(Long skuId) {
        HashMap<String, Object> result = new HashMap<>();

        //查询sku详情
        CompletableFuture<SkuInfo> future = CompletableFuture.supplyAsync(() -> {
            SkuInfo skuInfo = skuInfoFeignClient.getSkuInfo(skuId);
            result.put("skuInfo", skuInfo);
            return skuInfo;
        }, executor);


        //1~4可以交给线程池并行执行
        //1、查询分类信息
        CompletableFuture<Void> future1 = future.thenAcceptAsync((res) -> {
            if (res != null) {
                BaseCategoryView categoryView = skuInfoFeignClient.getCategoryView(res.getCategory3Id());
                result.put("categoryView", categoryView);
            }
        });

        //2、查询销售属性
        CompletableFuture<Void> future2 = future.thenAcceptAsync((res) -> {
            if (res != null) {
                List<SpuSaleAttr> spuSaleAttrListCheckBySku = skuInfoFeignClient.getSpuSaleAttrListCheckBySku(skuId, res.getSpuId());
                result.put("spuSaleAttrList", spuSaleAttrListCheckBySku);
            }
        });
        //3、查询价格
        CompletableFuture<Void> future3 = future.thenAcceptAsync((res) -> {
            if (res != null) {
                BigDecimal skuPrice = skuInfoFeignClient.getSkuPrice(skuId);
                result.put("price", skuPrice);
            }
        });
        //4、查询组合
        CompletableFuture<Void> future4 = future.thenAcceptAsync((res) -> {
            if (res != null) {
                Map map = skuInfoFeignClient.getSkuValueIdsMap(res.getSpuId());
                ObjectMapper mapper = new ObjectMapper();
                try {
                    String jsonStr = mapper.writeValueAsString(map);
                    log.info("valuesSkuJson内容：{}" + jsonStr);
                    if (jsonStr != null) {
                        result.put("valuesSkuJson", jsonStr);
                    }
                } catch (JsonProcessingException e) {
                    log.error("商品sku组合数据转换异常{}" + e);
                }
            }
        });
        //5、异步编排
        CompletableFuture<Void> allOf = CompletableFuture.allOf(future, future1, future2, future3, future4);

        //阻塞，等全完成再一起返回
        try {
            allOf.get();
        } catch (Exception e) {
            log.error("线程池异常：{}",e);
        }

        return result;
    }


    /**
     * 远程查询sku详细信息
     *
     * @param skuId
     * @return
     */
    private Map<String, Object> getFromServiceItemFeign01(Long skuId) {

        log.info("开始远程查询，远程会操作数据库。。。。");

        Map<String, Object> resultMap = new HashMap<>();

        // RPC 查询  skuDetail
        //1、Sku基本信息（名字，id，xxx，价格，sku_描述） sku_info
        //2，Sku图片信息（sku的默认图片[sku_info]，sku_image[一组图片]）
        SkuInfo skuInfo = skuInfoFeignClient.getSkuInfo(skuId);

        if (skuInfo != null) {
            resultMap.put("skuInfo", skuInfo);

            //3，Sku分类信息（sku_info[只有三级分类]，根据这个三级分类查出所在的一级，二级分类内容，连上三张分类表继续查）
            BaseCategoryView categoryView = skuInfoFeignClient.getCategoryView(skuInfo.getCategory3Id());
            if (categoryView != null) {
                resultMap.put("categoryView", categoryView);
            }

            //4，Sku销售属性相关信息（查出自己的sku组合，还要查出这个sku所在的spu定义了的所有销售属性和属性值）
            List<SpuSaleAttr> spuSaleAttrListCheckBySku = skuInfoFeignClient.getSpuSaleAttrListCheckBySku(skuId, skuInfo.getSpuId());
            if (spuSaleAttrListCheckBySku != null) {
                resultMap.put("spuSaleAttrList", spuSaleAttrListCheckBySku);
            }


            //5，Sku价格信息（平台可以单独修改价格，sku后续会放入缓存，为了回显最新价格，所以单独获取）
            BigDecimal skuPrice = skuInfoFeignClient.getSkuPrice(skuId);
            if (skuPrice != null) {
                resultMap.put("price", skuPrice);
            }

            //TODO  有可能前端这里还需要将map转换成json字符串
            //6、Spu下面的所有存在的sku组合信息{"121|123|156":"65","":""}
            Map map = skuInfoFeignClient.getSkuValueIdsMap(skuInfo.getSpuId());
            ObjectMapper mapper = new ObjectMapper();
            try {
                String jsonStr = mapper.writeValueAsString(map);
                log.info("valuesSkuJson内容：{}" + jsonStr);
                if (jsonStr != null) {
                    resultMap.put("valuesSkuJson", jsonStr);
                }
            } catch (JsonProcessingException e) {
                log.error("商品sku组合数据转换异常{}" + e);
            }
        }
        return resultMap;
    }
}
